#!/usr/bin/env perl

# Lame script to compare two build logs.
#
# The script intercepts directory changes and compiler invocations and
# compares the compiler invocations for equality. Equality is defined
# as "same options in both invocations ignoring order". So we only test
# a necessary condition.
#
# Both builds must be configured with the same --prefix. Otherwise,
# the value of -DVG_LIBDIR= will differ. They also need to use the same
# compiler.

use Getopt::Long;
use strict;
use warnings;

my $prog_name = "compare-build-logs";

my $usage=<<EOF;
USAGE

  $prog_name   log1 log2

    [--gcc]            intercept GCC invocations (this is default)

    [--clang]          intercept clang invocations

    [-v]               verbose mode

    log-file1

    log-file2
EOF

my $compiler = "gcc";
my $verbose  = 0;

GetOptions( "gcc"   => sub { $compiler = "gcc" },
            "clang" => sub { $compiler = "clang" },
            "v" => sub { $verbose = 1 },
            ) || die $usage;

my $num_arg = $#ARGV + 1;

if ($num_arg != 2) {
    die $usage;
}

my $log1  = $ARGV[0];
my $log2  = $ARGV[1];

# Hashes:  relative filename -> compiler invocation
my %cmd1 = read_log_file($log1);
my %cmd2 = read_log_file($log2);

# Compare the log files

foreach my $file (keys %cmd1) {
    if (! $cmd2{$file}) {
        print "*** $file missing in $log2\n";
    } else {
        compare_invocations($file, $cmd1{$file }, $cmd2{$file});
    }
}
foreach my $file (keys %cmd2) {
    if (! $cmd1{$file}) {
        print "*** $file missing in $log1\n";
    }
}

exit 0;

# Compare two lines |c1| and |c2| which are compiler invocations for |file|.
# Basically, we look at a compiler invocation as a sequence of blank-separated
# options.
sub compare_invocations {
    my ($file, $c1, $c2) = @_;
    my ($found1, $found2);
#    print "COMPARE $file\n";

# Remove stuff that has embedded spaces

# Remove: `test -f 'whatever.c' || echo './'`
    $c1 =~ s|(`[^`]*`)||;
    $found1 = $1;
    $c2 =~ s|(`[^`]*`)||;
    $found2 = $1;
    if ($found1 && $found2) {
        die if ($found1 ne $found2);
    }

# Remove: -o whatever
    $c1 =~ s|-o[ ][ ]*([^ ][^ ]*)||;
    $found1 = $1;
    $c2 =~ s|-o[ ][ ]*([^ ][^ ]*)||;
    $found2 = $1;
    if ($found1 && $found2) {
        die if ($found1 ne $found2);
    }

# The remaining lines are considered to be blank-separated options and file
# names. They should be identical in both invocations (but in any order).
    my %o1 = ();
    my %o2 = ();
    foreach my $k (split /\s+/,$c1) {
        $o1{$k} = 1;
    }
    foreach my $k (split /\s+/,$c2) {
        $o2{$k} = 1;
    }
#    foreach my $zz (keys %o1) {
#        print "$zz\n";
#    }
    foreach my $k (keys %o1) {
        if (! $o2{$k}) {
            print "*** '$k' is missing in compilation of '$file' in $log2\n";
        }
    }
    foreach my $k (keys %o2) {
        if (! $o1{$k}) {
            print "*** '$k' is missing in compilation of '$file' in $log1\n";
        }
    }
}

# Read a compiler log file.
# Return hash: relative (to build root) file name -> compiler invocation
sub read_log_file
{
    my ($log) = @_;
    my $dir = "";
    my $root = "";
    my %cmd = ();

    print "...reading $log\n" if ($verbose);
    
    open(LOG, "$log") || die "cannot open $log\n";
    while (my $line = <LOG>) {
        chomp $line;
        if ($line =~ /^make/) {
            if ($line =~ /Entering directory/) {
                $dir = $line;
                $dir =~ s/^.*`//;
                $dir =~ s/'.*//;
                if ($root eq "") {
                    $root = $dir;
                    # Append a slash if not present
                    $root = "$root/" if (! ($root =~ /\/$/));
                    print "...build root is $root\n" if ($verbose);
                }
#                print "DIR = $dir\n";
                next;
            }
        }
        if ($line =~ /^\s*[^\s]*$compiler\s/) {
            # If line ends in \ read continuation line.
            while ($line =~ /\\$/) {
                my $next = <LOG>;
                chomp($next);
                $line =~ s/\\$//;
                $line .= $next;
            }

            my $file = extract_file($line);
            $file = "$dir/$file";     # make absolute
            $file =~ s/$root//;       # remove build root
#            print "FILE $file\n";
            $cmd{"$file"} = $line;
        }
    }
    close(LOG);

    my $num_invocations = int(keys %cmd);

    if ($num_invocations == 0) {
        print "*** File $log does not contain any compiler invocations\n";
    } else {
        print "...found $num_invocations invocations\n" if ($verbose);
    }
    return %cmd;
}

# Extract a file name from the command line. Assume there is a -o filename
# present. If not, issue a warning.
#
sub extract_file {
    my($line) = @_;
# Look for -o executable
    if ($line =~ /-o[ ][ ]*([^ ][^ ]*)/) {
        return $1;
    } else {
        print "*** Could not extract file name from $line\n";
        return "UNKNOWN";
    }
}
